#include "hiredis.h"

HiredisClient::HiredisClient(zrpc::EventLoop *loop, const std::string &ip,
                             uint16_t port)
    : loop_(loop), ip_(ip), port_(port), context_(nullptr) {}

HiredisClient::~HiredisClient() {
  assert(!channel_ || channel_->IsNoneEvent());
  ::redisAsyncFree(context_);
}

bool HiredisClient::Connected() const {
  return channel_ && context_ && (context_->c.flags & REDIS_CONNECTED);
}

const char *HiredisClient::Errstr() const {
  assert(context_ != nullptr);
  return context_->errstr;
}

void HiredisClient::Connect() {
  assert(!context_);
  context_ = ::redisAsyncConnect(ip_.c_str(), port_);
  context_->ev.addRead = AddRead;
  context_->ev.delRead = DelRead;
  context_->ev.addWrite = AddWrite;
  context_->ev.delWrite = DelWrite;
  context_->ev.cleanup = Cleanup;
  context_->ev.data = this;
  SetChannel();

  assert(context_->onConnect == nullptr);
  assert(context_->onDisconnect == nullptr);
  ::redisAsyncSetConnectCallback(context_, RedisConnectCallback);
  ::redisAsyncSetDisconnectCallback(context_, RedisDisConnectCallback);
}

void HiredisClient::Disconnect() {
  if (Connected()) {
    ::redisAsyncDisconnect(context_);
  }
}

int HiredisClient::GetFd() const {
  assert(context_);
  return context_->c.fd;
}

void HiredisClient::SetChannel() {
  assert(!channel_);
  channel_.reset(new zrpc::Channel(loop_, GetFd()));
  channel_->SetReadCallback(std::bind(&HiredisClient::HandleRead, this));
  channel_->SetWriteCallback(std::bind(&HiredisClient::HandleWrite, this));
}

void HiredisClient::RemoveChannel() {
  channel_->DisableAll();
  channel_->Remove();
  channel_.reset();
}

void HiredisClient::HandleRead() { ::redisAsyncHandleRead(context_); }

void HiredisClient::HandleWrite() { ::redisAsyncHandleWrite(context_); }

void HiredisClient::RedisCommandCallback(redisAsyncContext *ac, void *r,
                                         void *privdata) {
  redisReply *reply = static_cast<redisReply *>(r);
  CommandCallback *cb = static_cast<CommandCallback *>(privdata);
  GetHiredis(ac)->ReplyCommandCallback(reply, cb);
}

void HiredisClient::RedisDisConnectCallback(const redisAsyncContext *ac,
                                       int status) {
  GetHiredis(ac)->ReplyDisconnectCallback(status);
}

void HiredisClient::RedisConnectCallback(const redisAsyncContext *ac,
                                           int status) {
  GetHiredis(ac)->ReplyConnectCallback(status);
}

void HiredisClient::ReplyConnectCallback(int status) {
  if (status != REDIS_OK) {
  } else {
  }

  if (connect_cb_) {
    connect_cb_(this, status);
  }
}

void HiredisClient::ReplyCommandCallback(redisReply *reply,
                                         CommandCallback *cb) {
  (*cb)(this, reply);
  delete cb;
}

void HiredisClient::ReplyDisconnectCallback(int status) {
  RemoveChannel();
  if (disconnect_cb_) {
    disconnect_cb_(this, status);
  }
}

HiredisClient *HiredisClient::GetHiredis(const redisAsyncContext *ac) {
  HiredisClient *hiredis = static_cast<HiredisClient *>(ac->ev.data);
  assert(hiredis->context_ == ac);
  return hiredis;
}

void HiredisClient::AddRead(void *privdata) {
  HiredisClient *hiredis = static_cast<HiredisClient *>(privdata);
  hiredis->channel_->EnableReading();
}

void HiredisClient::DelRead(void *privdata) {
  HiredisClient *hiredis = static_cast<HiredisClient *>(privdata);
  hiredis->channel_->DisableReading();
}

void HiredisClient::AddWrite(void *privdata) {
  HiredisClient *hiredis = static_cast<HiredisClient *>(privdata);
  hiredis->channel_->EnableWriting();
}

void HiredisClient::DelWrite(void *privdata) {
  HiredisClient *hiredis = static_cast<HiredisClient *>(privdata);
  hiredis->channel_->DisableWriting();
}

void HiredisClient::Cleanup(void *privdata) {
  HiredisClient *hiredis = static_cast<HiredisClient *>(privdata);
  LOG_INFO << hiredis;
}

int HiredisClient::Command(const CommandCallback &cb, std::string_view cmd,
                           ...) {
  CommandCallback *p = new CommandCallback(cb);
  va_list args;
  va_start(args, cmd.data());
  int ret =
      ::redisvAsyncCommand(context_, RedisCommandCallback, p, cmd.data(), args);
  va_end(args);
  return ret;
}

void HiredisClient::PingCallback(HiredisClient *msg, redisReply *reply) {
  assert(this == msg);
  LOG_INFO << reply->str;
}

int HiredisClient::Ping() {
  return Command(std::bind(&HiredisClient::PingCallback, this,
                           std::placeholders::_1, std::placeholders::_2),
                 "ping");
}
